package compilers.AST.statement;

import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.Node;
import compilers.AST.basics.Identifier;
import compilers.AST.basics.Parameter;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableFunctionElement;
import compilers.symbolTable.SymbolTableScope;

public class FunctionDefinitionStatement extends Statement {
	
	private TypeSpecifier fReturnType;
	private Identifier fIdentifier;
	private LinkedList<Parameter> fParameters;
	private LinkedList<Statement> fStatements;

	public FunctionDefinitionStatement() {
		fParameters = new LinkedList<Parameter>();
		fStatements = new LinkedList<Statement>();
	}
	
	public FunctionDefinitionStatement(int lineNr) {
		super(lineNr);
		fParameters = new LinkedList<Parameter>();
		fStatements = new LinkedList<Statement>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "FunctionDefinitionStatement");
		fReturnType.print(identLevel+1);
		fIdentifier.print(identLevel+1);
		Iterator<Parameter> it = fParameters.iterator();
		while (it.hasNext()) {
			it.next().print(identLevel+1);
		}
		Iterator<Statement> it2 = fStatements.iterator();
		while (it2.hasNext()) {
			it2.next().print(identLevel+1);
		}
	}
	
	public void setReturnType(TypeSpecifier returnType) {
		fReturnType = returnType;
	}
	
	public void setIdentifier(Identifier ident) {
		fIdentifier = ident;
	}
	
	public Identifier getIdentifier() {
		return fIdentifier;
	}
	
	public TypeSpecifier getReturnType() {
		return fReturnType;
	}
	
	public void addParameter(Parameter param) {
		fParameters.add(param);
	}
	
	public void addStatement(Statement statement) {
		fStatements.add(statement);
	}
	
	public LinkedList<Parameter> getParameters() {
		return fParameters;
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		SymbolTableScope newScope = currentScope.getChildScope(this);
		int returnType = fReturnType.getType(currentScope);
		
		//Check statements
		Iterator<Statement> it = fStatements.iterator();
		Statement curStatement;
		boolean returnOk = true;
		boolean atLeastOne = false;
		
		while (it.hasNext()) {
			curStatement = it.next();
			curStatement.checkSemantics(newScope);
			
			if(curStatement instanceof ConditionStatement) {
				//Every condition must have its own return statement of the correct type..
				if(returnOk) {
					returnOk = ((ConditionStatement) curStatement).checkReturn(newScope, returnType, fIdentifier.getValue());
					atLeastOne = true;
				}
			} else if(curStatement instanceof ReturnStatement) {
				returnOk = true;
				atLeastOne = true;
				ReturnStatement retStatement = (ReturnStatement) curStatement;
				
				//Void function but return statement given
				if(fReturnType.getType(currentScope) == TypeSpecifier.VOID) {
					throw new SemanticalException("No return statement allowed in function '" + fIdentifier.getValue() + "'", retStatement.getLine());
				}

				//Check for wrong return type
				int retType = retStatement.getResultType(newScope);
				if(returnType != retType) {
					TypeSpecifier temp = new TypeSpecifier(retType);
					throw new SemanticalException("Return statement of type '" + temp.getTypeName() + "' doesn't match with the required return type '" + fReturnType.getTypeName() + "' of function '" + fIdentifier.getValue() + "'", retStatement.getLine());
				}
			}
		}
		
		//Verify whether there is at least one return statement
		if((fReturnType.getType(currentScope) != TypeSpecifier.VOID) && (!returnOk || !atLeastOne)) {
			throw new SemanticalException("No return statement of type '" + fReturnType.getTypeName() + "' in function '" + fIdentifier.getValue() + "' given.", fLineNr);
		}		
	}
	
	//TODO: Why is this to be used?
	public void compareWithForwardDeclaration(SymbolTableFunctionElement forwardDecl) throws SemanticalException {
		
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		SymbolTableScope newScope = currentScope.getChildScope(this);
		
		//If this is a nested function, jump to the end of the function
		if(newScope.getDepth() > 0) {
			pGen.unconditionalJump("end_" + fIdentifier.getValue());
		}
		pGen.beginLabel(fIdentifier.getValue());
				
		// Allocate memory for local variables
		pGen.ssp(newScope.calcStaticElements());
		
		//Generate p-code for statements
		Iterator<Statement> it = fStatements.iterator();
		while (it.hasNext()) {
			it.next().generatePcode(pGen, newScope);
		}
		
		//Exit instruction for void-functions
		if(fReturnType.getType(currentScope) == TypeSpecifier.VOID) {
			pGen.retf();
		}
		pGen.endLabel(fIdentifier.getValue());
	}

}
